//6.1.2
/**
 * @param {string} str1
 * @remark 构造函数
 */
function f1(str1) {
    var Vehicle = function () {
        this.price = 1000;
        return this.price;
    };
    return  Vehicle();
}
// console.log(f1())//1000

//6.1.3-1
/**
 * @param {string} str2
 * @remark new命令的作用，就是执行构造函数，返回一个实例对象
 */
function f2(str2) {
    var Vehicle = function () {
        this.price = 1000;
    };
      
    var v = new Vehicle();
    return v;
}

/**
 * @param {string} str3
 * @remark 如果忘了使用new命令,this这时代表全局对象
 */
function f3(str3) {
    var Vehicle = function () {
        this.price = 1000;
    };
      
    var v = Vehicle();//相当于构造了一个空对象，并没有返回，所以v是undefined
    // var v = Vehicle;//相当于让v=Vehicle，返回的是一个函数，所以v是函数[Function: Vehicle]
    // return v;
    return price//变成全局变量了
}

/**
 * @param {string} str4
 * @remark 构造函数内部判断是否使用new命令，如果发现没有使用，则直接返回一个实例对象。
 */
function f4(str4) {
    function Fubar(foo, bar) {
        if (!(this instanceof Fubar)) {
          return new Fubar(foo, bar);
        }
      
        this._foo = foo;
        this._bar = bar;
      }
    return {
        s1:Fubar(1, 2)._foo,
        s2:(new Fubar(1, 2))._foo
    }
}

// console.log(f2())//Vehicle { price: 1000 }
// console.log(f3())//1000
// console.log(f4())//{ s1: 1, s2: 1 }

//6.1.3-2
/**
 * @param {string} str5
 * @remark 如果构造函数内部有return语句，而且return后面跟着一个对象，new命令会返回return语句指定的对象；否则，就会不管return语句，返回this对象。
 * @remark 构造函数Vehicle的return语句返回一个数值。这时，new命令就会忽略这个return语句，返回“构造”后的this对象。
 */
function f5(str5) {
    var Vehicle = function () {
        this.price = 1000;
        return 1000;//1000是数值，非对象
    };
      
    return (new Vehicle()) === 1000
}

/**
 * @param {string} str6
 * @remark 如果return语句返回的是一个跟this无关的新对象，new命令会返回这个新对象，而不是this对象。这一点需要特别引起注意。
 */
function f6(str6) {
    var Vehicle = function (){
        this.price = 1000;
        return { price: 2000 };
    };
      
    return (new Vehicle()).price
}

/**
 * @param {string} str7
 * @remark 如果对普通函数（内部没有this关键字的函数）使用new命令，则会返回一个空对象。
 */
function f7(str7) {
    function fubar() {
        return 1000;
    }
    var a=new fubar()
    return a 
}
// console.log(f5())//false
// console.log(f6())//2000
// console.log(f7())//fubar {}

//6.1.3-3
/**
 * @param {string} str8
 * @remark 函数内部可以使用new.target属性。如果当前函数是new命令调用，new.target指向当前函数，否则为undefined。
 */
function f8(str8) {
    function f() {
        console.log(new.target === f);
    }
    f();
    new f();
}
// console.log(f8())
/*
false
true
undefined
*/  

//6.1.4
/**
 * @param {string} str9
 * @remark 希望以这个现有的对象作为模板，生成新的实例对象，这时就可以使用Object.create()方法。
 */
function f9(str9) {
    var person1 = {
        name: '张三',
        age: 38,
        greeting: function() {
          return ('Hi! I\'m ' + this.name + '.');
        }
    };
      
    var person2 = Object.create(person1);
      
    return {
        s1:person2.name,
        s2:person2.greeting()
    }
}
// console.log(f9())//{ s1: '张三', s2: "Hi! I'm 张三." }

//6.2.1
/**
 * @param {string} str10
 * @remark this就是属性或方法“当前”所在的对象。
 */
function f10(str10) {
    var person = {
        name: '张三',
        describe: function () {
          return '姓名：'+ this.name;
        }
    };
    return person.describe()
}
/**
 * @param {string} str11
 * @remark 由于对象的属性可以赋给另一个对象，所以属性所在的当前对象是可变的，即this的指向是可变的。
 */
function f11(str11) {
    var A = {
        name: '张三',
        describe: function () {
          return '姓名：'+ this.name;
        }
    };
      
    var B = {
        name: '李四'
    };
    
    B.describe = A.describe;
    return B.describe()
}
// console.log(f10())//姓名：张三
// console.log(f11())// 姓名：李四

//6.2.2
/**
 * @pram {string} str12
 * @remark 函数f在全局环境执行，this.x指向全局环境的x；在obj环境执行，this.x指向obj.x。
 */
function f12(str12) {
    var f = function () {
        console.log(this.x);
    }
      
    var x = 1;
    var obj = {
        f: f,
        x: 2,
    };
    return {
        s1:f(),
        s2:obj.f()
    }
}
// console.log(f12())

//6.2.3
/**
 * @param {string} str13
 * @remark 全局环境使用this，它指的就是顶层对象window。
 */
function f13(str13) {

    function f() {
        console.log(this === window);
    }
    return f() 
}
// console.log(f13())//node.js没有window对象，所以报错

//6.2.4-1
/**
 * @param {string} str14
 * @remark 避免多层 this
 */
function f14(str14) {
    var o = {
        f1: function() {
          console.log(this);
          var that = this;
          var f2 = function() {
            console.log(that);
          }();
        }
      }
      
    return o.f1()
}
// console.log(f14())
/*
{ f1: [Function: f1] }
{ f1: [Function: f1] }
undefined
*/

//6.2.4-2
/**
 * @param {string} str15
 * @remark 避免数组处理方法中的 this
 * @remark 就是前面提到的，使用中间变量固定this。
 */
function f15(str15) {
    var o = {
        v: 'hello',
        p: [ 'a1', 'a2' ],
        f: function f() {
          var that = this;
          this.p.forEach(function (item) {
            console.log(that.v+' '+item);
          });
        }
    }
      
    return o.f()
}

/**
 * @param {string} str16
 * @remark this当作foreach方法的第二个参数，固定它的运行环境。
 */
function f16(str16) {
    var obj= {
        v: 'hello',
        p: [ 'a1', 'a2' ],
        f: function f() {
          this.p.forEach(function (item) {
            console.log(this.v+' '+item);
          }, this);
        }
    }

    return obj.f()
}
// console.log(f15())
/*
hello a1
hello a2
undefined*/
// console.log(f16())
/*
hello a1
hello a2
undefined*/

//6.2.4-3
/**
 * @param {string} str17
 * @remark 避免回调函数中的 this 
 */
function f17(str17) {
    var o = {
        v: 'hello',
        p: [ 'a1', 'a2' ],
        f: function f() {
          this.p.forEach(function (item) {
            console.log(this.v+' '+item);
          });
        }
    }
      
    return o.f()
}
// console.log(f17())
/*
undefined a1
undefined a2
undefined
*/

//6.2.5-1
/**
 * @param {string} str18
 * @remark 函数实例的call方法，可以指定函数内部this的指向（即函数执行时所在的作用域），然后在所指定的作用域中，调用该函数。
 * @remark 全局环境运行函数f时，this指向全局环境（浏览器为window对象）；call方法可以改变this的指向，指定this指向对象obj，然后在对象obj的作用域中运行函数f。
 */
function f18(str18) {
    var obj = {};

    var f = function () {
      return this;
    };
    return f.call(obj) === obj
}

/**
 * @param {string} str19
 * @remark call方法的参数，应该是一个对象。如果参数为空、null和undefined，则默认传入全局对象。
*/
function f19(str19) {
    var n = 123;
    var obj = { n: 456 };
    
    function a() {
      return(this.n);
    }
    return{
        s1:a.call(null),
        s2:a.call(undefined),
        s3:a.call(),
        s4:a.call(obj)
    }
}

/**
 * @param {string} str20
 * @remark 如果call方法的参数是一个原始值，那么这个原始值会自动转成对应的包装对象，然后传入call方法
 */
function f20(str20) {
    var f = function () {
        return this;
    };
      
    return f.call(5)
}

/**
 * @param {string} str21
 * @remark call的第一个参数就是this所要指向的那个对象，后面的参数则是函数调用时所需的参数。
 */
function f21(str21) {
    function add(a, b) {
        return a + b;
    }
      
    return add.call(this, 1, 2)
}

/**
 * @param {string} str22
 * @remark call方法的一个应用是调用对象的原生方法。
 */
function f22(str22) {
    var obj = {};
    obj.hasOwnProperty = function () {
        return true;
    };
    return {
        s1:obj.hasOwnProperty('toString'),
        s2:Object.prototype.hasOwnProperty.call(obj, 'toString')
    }
}
// console.log(f18())//true
// console.log(f19())//{ s1: undefined, s2: undefined, s3: undefined, s4: 456 }
// console.log(f20())//[Number: 5]
// console.log(f21())//3
// console.log(f22())//{ s1: true, s2: false }

//6.2.5-2
/**
 * @param {string} str23
 * @remark apply方法的作用与call方法类似，也是改变this指向，然后再调用该函数。唯一的区别就是，它接收一个数组作为函数执行时的参数，使用格式如下。
 * @remark 在call方法中必须一个个添加，但是在apply方法中，必须以数组形式添加。
 */
function f23(str23) {
    function f(x, y){
        return(x + y);
    }
    return {
        s1:f.call(null, 1, 1) ,
        s2:f.apply(null, [1, 1])
    }
}
// console.log(f23())//{ s1: 2, s2: 2 }

//6.2.5-3
/**
 * @param {string} str24
 * @remark bind()方法用于将函数体内的this绑定到某个对象，然后返回一个新函数。
 */
function f24(str24) {
    var counter = {
        count: 0,
        inc: function () {
          this.count++;
        }
    };
      
    var func = counter.inc.bind(counter);
    func();
    return counter.count
}

/**
 * @param {string} str25
 * @remark 回调函数是 JavaScript 最常用的模式之一，但是一个常见的错误是，将包含this的方法直接当作回调函数。解决方法就是使用bind()方法，将counter.inc()绑定counter。
 */
function f25(str25) {
    var counter = {
        count: 0,
        inc: function () {
          'use strict';
          this.count++;
        }
    };
      
    function callIt(callback) {
        callback();
    }
    callIt(counter.inc.bind(counter));
    return counter.count
}
// console.log(f24())//1
// console.log(f25())//1

//6.3.1-1
/**
 * @param {string} str26
 * @remark 同一个构造函数的多个实例之间，无法共享属性，从而造成对系统资源的浪费。
 */
function f26(str26) {
    function Cat(name, color) {
        this.name = name;
        this.color = color;
        this.meow = function () {
          console.log('喵喵');
        };
    }
      
    var cat1 = new Cat('大毛', '白色');
    var cat2 = new Cat('二毛', '黑色');
      
    return cat1.meow === cat2.meow
}
// console.log(f26())//false

//6.3.1-2
/**
 * @param {string} str27
 * @remark JavaScript 规定，每个函数都有一个prototype属性，指向一个对象。
 */
function f27(str27) {
    function f() {}
    return typeof f.prototype
}

/**
 * @param {string} str28
 * @remark 对于构造函数来说，生成实例的时候，该属性会自动成为实例对象的原型。
 */
function f28(str28) {
    function Animal(name) {
        this.name = name;
      }
    Animal.prototype.color = 'white';
      
    var cat1 = new Animal('大毛');
    var cat2 = new Animal('二毛');
      
    return cat1.color === cat2.color
}
// console.log(f27())//object
// console.log(f28())//true

//6.3.1-3
/**
 * @param {string} str29
 * @remark Object.prototype的原型是null。null没有任何属性和方法，也没有自己的原型。因此，原型链的尽头就是null。
 */
function f29(str29) {
    return Object.prototype.__proto__ === null
}

/**
 * @param {string} str30
 * @remark 如果让构造函数的prototype属性指向一个数组，就意味着实例对象可以调用数组方法。
 */
function f30(str30) {
    var MyArray = function () {};

    MyArray.prototype = new Array();
    MyArray.prototype.constructor = MyArray;
    
    var mine = new MyArray();
    mine.push(1, 2, 3);
    return{
        s1:mine.length ,
        s2:mine instanceof Array 
    }
}
// console.log(f29())//true
// console.log(f30())//{ s1: 3, s2: true }

//6.3.1-4
/**
 * @param {string} str31
 * @remark prototype对象有一个constructor属性，默认指向prototype对象所在的构造函数。
 */
function f31(str31) {
    function P() {}
    return P.prototype.constructor === P 
}

/**
 * @param {string} str32
 * @remark constructor属性的作用是，可以得知某个实例对象，到底是哪一个构造函数产生的。
 */
function f32(str32) {
    function P() {}
    var p = new P();
    return p.constructor === P
}

/**
 * @param {string} str33
 * @remark 有了constructor属性，就可以从一个实例对象新建另一个实例。
 */
function f33(str33) {
    function P() {}
    var p = new P();
    var p2 = new p.constructor();
    return p2.constructor === P
}

/**
 * @param {string} str34
 * @remark constructor属性表示原型对象与构造函数之间的关联关系，如果修改了原型对象，一般会同时修改constructor属性
 */
function f34(str34) {
    function Person(name) {
        this.name = name;
    }
      
    Person.prototype.constructor === Person // true
      
    Person.prototype = {
        method: function () {}
    };//改掉原型对象
      
    return{
        s1:Person.prototype.constructor === Person,
        s2:Person.prototype.constructor === Object
    }
}

/**
 * @param {string} str35
 * @remark 不能确定constructor属性是什么函数，还有一个办法：通过name属性，从实例得到构造函数的名称。
 */
function f35(){function Foo() {}
    var f = new Foo();
    return f.constructor.name
}
// console.log(f31())//true
// console.log(f32())//true
// console.log(f33())//true
// console.log(f34())//{ s1: false, s2: true }
// console.log(f35())//Foo

// 6.1.2 
/**
 * @param {string} str36
 * @remark instanceof运算符返回一个布尔值，表示对象是否为某个构造函数的实例。
 * @remark instanceof运算符可以判断一个值是否为非null的对象。
 */
function f36(str36){
    var obj = { foo: 123 };
    return{
        s1:obj instanceof Object,
        s2:null instanceof Object 
    }
}
/**
 * @param {string} str37
 * @remark instanceof运算符的一个用处，是判断值的类型。
 * @remark instanceof运算符只能用于对象，不适用原始类型的值。
 */
function f37(str37){
    var obj = { foo: 123 };
    var a='hello'
    return{
        s1:obj instanceof Object,
        s2:a instanceof String 
    }
}

/**
 * @param {string} str38
 * @remark 利用instanceof运算符，还可以巧妙地解决，调用构造函数时，忘了加new命令的问题。
 */
function f38(str38){
    function Fubar (foo, bar) {
        if (this instanceof Fubar) {
          this._foo = foo;
          this._bar = bar;
        } else {
          return new Fubar(foo, bar);
        }
    }
}
// console.log(f36())//{ s1: true, s2: false }
// console.log(f37())//{ s1: true, s2: false }


// 6.3.3
/**
 * @param {string} str39
 * @remark 构造函数的继承
 */
function f39(str39){
    function Shape() {
        this.x = 0;
        this.y = 0;
    }
      
    Shape.prototype.move = function (x, y) {
        this.x += x;
        this.y += y;
        console.info('Shape moved.');
    };
    function Rectangle() {
        Shape.call(this); // 调用父类构造函数
    }
    Rectangle.prototype = Object.create(Shape.prototype);
    Rectangle.prototype.constructor = Rectangle;
    var rect = new Rectangle();
    return {
        s1:rect instanceof Rectangle ,
        s2:rect instanceof Shape
    }
}
// console.log(f39())//{ s1: true, s2: true }

//6.3.4
/**
 * @param {string}str40
 * @remark 多重继承
 */
function f40(){
    function M1() {
        this.hello = 'hello';
    }
  
    function M2() {
        this.world = 'world';
    }
  
    function S() {
        M1.call(this);
        M2.call(this);
    }
  
    // 继承 M1
    S.prototype = Object.create(M1.prototype);
    // 继承链上加入 M2
    Object.assign(S.prototype, M2.prototype);
  
    // 指定构造函数
    S.prototype.constructor = S;
  
    var s = new S();
    return s.world 
}
// console.log(f40());// world

//6.4.2
/**
 * @param {string} str41
 * @remark Object.setPrototypeOf方法为参数对象设置原型，返回该参数对象。它接受两个参数，第一个是现有对象，第二个是原型对象。
 */
function f41(str41){
    var a = {};
    var b = {x: 1};
    Object.setPrototypeOf(a, b);
    
    return {
        s1:Object.getPrototypeOf(a) === b ,
        s2:a.x 
    }
}
// console.log(f41())//{ s1: true, s2: 1 }

//6.4.3
/**
 * @param {string} str42
 * @remark JavaScript 提供了Object.create()方法，用来满足这种需求。该方法接受一个对象作为参数，然后以它为原型，返回一个实例对象。该实例完全继承原型对象的属性。
 */
function f42(str42){
    // 原型对象
    var A = {
        print: function () {
            return 'hello';
        }
    };
  
    // 实例对象
    var B = Object.create(A);
    return{
        s1:Object.getPrototypeOf(B) === A ,
        s2:B.print(), 
        s3:B.print === A.print
    }
}

/**
 * @param {string} str43
 * @remark 想要生成一个不继承任何属性（比如没有toString()和valueOf()方法）的对象，可以将Object.create()的参数设为null。
 * @remark 使用Object.create()方法的时候，必须提供对象原型，即参数不能为空，或者不是对象，否则会报错
 */
function f43(str43){
    var obj = Object.create(null);

    return obj.valueOf()
}

/**
 * @param {string} str44
 * @remarkObject.create()方法生成的新对象，动态继承了原型。在原型上添加或修改任何方法，会立刻反映在新对象之上 构造函数的继承
 */
function f44(str44){
    var obj1 = { p: 1 };
    var obj2 = Object.create(obj1);
    
    obj1.p = 2;
    return obj2.p 
}

/**
 * @param {string} str45
 * @remark 除了对象的原型，Object.create()方法还可以接受第二个参数。该参数是一个属性描述对象，它所描述的对象属性，会添加到实例对象，作为该对象自身的属性。
 * @remark Object.create()方法生成的对象，继承了它的原型对象的构造函数。
 */
function f45(str45){
    function A() {}
    var a = new A();
    var b = Object.create(a);
    
    return{
        s1:b.constructor === A, 
        s2:b instanceof A 
    }
}
// console.log(f42())//{ s1: true, s2: 'hello', s3: true }
// console.log(f43())// TypeError: Object [object Object] has no method 'valueOf'
// console.log(f44())// 2
// console.log(f45())//{ s1: true, s2: true }

//6.4.4
/**
 * @param {string} str46
 * @remark 实例对象的isPrototypeOf方法，用来判断该对象是否为参数对象的原型。
 */
function f46(str46){
    var o1 = {};
    var o2 = Object.create(o1);
    var o3 = Object.create(o2);
    
    return{
        s1:o2.isPrototypeOf(o3), 
        s2:o1.isPrototypeOf(o3) 
    }
}
// console.log(f46())//{ s1: true, s2: true }

//6.4.5
/**
 * @param {string} str47
 * @remark 实例对象的__proto__属性（前后各两个下划线），返回该对象的原型。该属性可读写。
 */
function f47(str47){
    var obj = {};
    var p = {};
    
    obj.__proto__ = p;
    return Object.getPrototypeOf(obj) === p 
}
// console.log(f47())//true

//6.4.7
/**
 * @param {string} str48
 * @remark Object.getOwnPropertyNames方法返回一个数组，成员是参数对象本身的所有属性的键名，不包含继承的属性键名。
 */
function f48(str48){
    return Object.getOwnPropertyNames(Date)
}
// console.log(f48())//[ 'length', 'name', 'prototype', 'now', 'parse', 'UTC' ]

//6.4.8
/**
 * @param {string} str49
 * @remark 对象实例的hasOwnProperty方法返回一个布尔值，用于判断某个属性定义在对象自身，还是定义在原型链上
 */
function f49(str49){
    return{
        s1:Date.hasOwnProperty('length') ,
        s2:Date.hasOwnProperty('toString')
    }
}
// console.log(f49())//{ s1: true, s2: false }

//6.4.9
/**
 * @param {string} str50
 * @remark in运算符返回一个布尔值，表示一个对象是否具有某个属性。它不区分该属性是对象自身的属性，还是继承的属性。
 */
function f50(str50){
    return{
        s1:'toString' in Date, 
        s2:'name' in Date
    }
}

/**
 * @param {string} str51
 * @remark 获得对象的所有属性（不管是自身的还是继承的，也不管是否可枚举），可以使用下面的函数。
 */
function f51(str51){
    function inheritedPropertyNames(obj) {
        // 创建一个空对象props用于存储继承的属性名
        var props = {};
        
        // 使用循环逆序遍历obj的原型链
        while(obj) {
          // 获取obj的所有属性名，并将这些属性名对应的值设为true，表示已经继承
          Object.getOwnPropertyNames(obj).forEach(function(p) {
            props[p] = true;
          });
          
          // 将obj指向下一级原型
          obj = Object.getPrototypeOf(obj);
        }
        
        // 返回props的所有属性名，即继承的属性名
        return Object.getOwnPropertyNames(props);
      }
}
// console.log(f50())//{ s1: true, s2: true }

//6.4.10
/**
 * @param {string} str52
 * @remark 对象拷贝
 */
function f52(str52){
    function copyObject(orig) {
        return Object.create(
          // 使用 Object.getPrototypeOf() 获取原始对象的原型
          Object.getPrototypeOf(orig),
          // 使用 Object.getOwnPropertyDescriptors() 获取原始对象的所有属性描述符
          Object.getOwnPropertyDescriptors(orig)
        );
      }
}

//6.52.
/**
 * @param {string} str53
 * @remark 严格模式
 */
function f53(str53){
    function strict() {
        'use strict';
        return '这是严格模式';
    }
      
    function strict2() {
        'use strict';
        function f() {
          return '这也是严格模式';
        }
        return f();
    }
      
    function notStrict() {
        return '这是正常模式';
    }
    return{
        s1:strict(),
        s2:strict2(),
        s3:notStrict()
    }
}
// console.log(f53())//{ s1: '这是严格模式', s2: '这也是严格模式', s3: '这是正常模式' }

//6.5.4-2
/**
 * @param {string} str54
 * @remark 正常模式下，函数内部的this可能会指向全局对象，严格模式禁止这种用法，避免无意间创造全局变量。
 */
function f54(str54){
    function f() {
        'use strict';
        return this === undefined;
    }
    return f()
}

/**
 * @param {string} str54
 * @remark 使用构造函数时，有时忘了加new，这时this不再指向全局对象，而是报错。
 */
function f55(str55){
    function f() {
        'use strict';
        var a = 1;
        return this.a;
    }
    return f()
}
// console.log(f54())//true
// console.log(f55())//Uncaught TypeError: Cannot read property 'a' of undefined

//6.5.5-1
/**
 * @param {string} str56
 * @remark 严格模式下，使用with语句将报错。因为with语句无法在编译时就确定，某个属性到底归属哪个对象，从而影响了编译效果。
 * @remark 严格模式下，eval语句本身就是一个作用域，不再能够在其所运行的作用域创设新的变量了，也就是说，eval所生成的变量只能用于eval内部。
 */
function f56(str56){
    (function () {
        'use strict';
        var x = 2;
        console.log(eval('var x = 5; x')) // 5
        console.log(x) // 2
      })()
}
// console.log(f56())
/*
5
2
undefined*/